package org.sjsu.freedoor.db;

import com.mongodb.*;

import java.util.regex.Pattern;

import org.sjsu.freedoor.model.*;
import org.bson.types.ObjectId;


public class MongoHandler implements DBHandler{

	private Mongo mongo;
	private DB db;
	
	private String COLLNAME_COM = "communities";
	private String COLLNAME_SER = "services";
	private String COLLNAME_GDS = "goods";
	
	private String KEY_URL = "community_url";
	private String KEY_ID = "id";
	private String KEY_NAME = "name";
	private String KEY_TYPE = "type";
	private String KEY_DESC = "description";
	private String KEY_STAT = "status";
	private String KEY_TSTMP = "timestamp";
	
	public MongoHandler() {
		try {
			mongo = new Mongo();
			db = mongo.getDB("adjsdb");
		} catch (Exception e) {
			e.printStackTrace();
		}		
	}
	
	/*	'Communities' related methods	*/
	public CommunityList getCommunities() {
//		DBCollection coms = db.getCollection("communities");
//		DBCursor cursor = coms.find();
//		
//		System.out.println("count: " + coms.count());
//		
//        try {
//            while(cursor.hasNext()) {
//                System.out.println(cursor.next());
//            }
//        } finally {
//            cursor.close();
//        }
//		
//		return coms.toString();

		DBCollection col = db.getCollection(COLLNAME_COM);
		DBCursor cursor = col.find();
		
		System.out.println("count: " + col.count());
		
		CommunityList coms = new CommunityList();
		
        try {
            while(cursor.hasNext()) {
            	cursor.next();
            	String smsg = "{\"description\":\"" + cursor.curr().get("description") + "\",\"url\":\"" + cursor.curr().get("url") +"\"}";  
                System.out.println(smsg);
                Community com = new Community((String)cursor.curr().get("description"), (String)cursor.curr().get("url"));
                coms.addCommunity(com);
            }
        } finally {
            cursor.close();
        }
		
		return coms;
		
	}
	
	public boolean postCommunity(Community c) {
    	if ( c == null ) {
    		return false;
    	}
        
    	//construct the doc
    	BasicDBObject doc = new BasicDBObject();
        doc.put("description", c.getDescription());
        doc.put("url", c.getURL());
        
        //  Get the services collection (creates one if one such collection is not present)
        DBCollection com_coll = db.getCollection(COLLNAME_COM);
        
        // do an upsert
        BasicDBObject query = new BasicDBObject();
        query.put("url", c.getURL());
        
        DBCursor cursor = com_coll.find(query);
        
        if(cursor.size() > 0) {
        	System.out.println("updating..");
        	com_coll.findAndModify(query, doc);
        }
        else{
        	System.out.println("inserting..");
        	com_coll.insert(doc);
        }
        
      	com_coll.update(query, doc, true, true);
      
		return true;
	}
	
	
	/*	'Services' related methods	*/
	
	// Insert a service document under newly generated ObjectID, returns ID
    private String insertService(Service service) {
    	
    	if ( service == null )
    		return "";
        
        //  Get the services collection (creates one if one such collection is not present)
        DBCollection serv_coll = db.getCollection(COLLNAME_SER);
        
        //  Need to parse the 'body' json and get the values.
        BasicDBObject doc = new BasicDBObject();
        
        doc.put("community_url", service.getURL());
        doc.put("id", service.getId());
        doc.put("name", service.getName());
        doc.put("type", service.getType());
        doc.put("description", service.getDescription());
        doc.put("status", service.getStatus());
        doc.put("timestamp", service.getTimeStamp());
        doc.put("offers", service.getOffers().toJsonString(false, false));
        
        serv_coll.insert(doc);
        System.out.println("The inserted services doc = " + doc.toString());
        
        if(service.getId().isEmpty() && (service.getURL().isEmpty() || service.getURL().equals(Freedoor.SELF_URL))) {
        	doc.put("id", doc.get("_id").toString()); //set to generated ID if local
        	doc.put("community_url", Freedoor.SELF_URL); //set to local URL
        	//service.setId(doc.get("_id").toString()); //set to generated ID if local
        	//service.setURL(Freedoor.SELF_URL); //set to local URL
        	BasicDBObject query = new BasicDBObject();
            query.put("_id", doc.get("_id"));
        	serv_coll.update(query, doc);
        }
        
        return doc.get("_id").toString();
    }
    
	//  Updates a service document if it exists, inserts otherwise
 	// If Local URL and ID matches an existing record, updates record and returns ID of existing record
    // Insert a service document under newly generated ObjectID, returns ID
 	// If error of some kind, returns ""
    public String putService(Service service) {
    	
    	String updateId = updateService(service);
        
    	if (updateId.isEmpty())
    		return insertService(service);
    	else
    		return updateId;
    }
    
    //  Updates a service document if it exists, inserts otherwise
 	// If Local URL and ID matches an existing record, updates record and returns ID of existing record
 	// If error of some kind or no match, returns ""
    public String updateService(Service service) {
    	if ( service == null )
    		return "";
    	
    	//Try retrieving the service with the service.id
        DBCollection serv_coll = db.getCollection(COLLNAME_SER);
        
        BasicDBObject query = new BasicDBObject();
        query.put("type", "services");
        query.put("community_url", service.getURL());
        query.put("id", service.getId());
        DBObject doc = serv_coll.findOne(query);
        
    	if ( doc != null ) {
    		//	Update
            doc.put("community_url", service.getURL());
            doc.put("id", service.getId());
            doc.put("name", service.getName());
            doc.put("type", service.getType());
            doc.put("description", service.getDescription());
            doc.put("status", service.getStatus());
            doc.put("timestamp", service.getTimeStamp());
            doc.put("offers", service.getOffers().toJsonString(false, false));
            
            serv_coll.update(query, doc);
            
            return doc.get("_id").toString();
    	} else
    		return "";
    }
    
    //  Retrieve a services doc based on 'id'
    public Service getService(String oid) {
    	DBObject doc = getServiceObj(oid);
    	
    	if(doc == null)
    		return null;
    	
    	Service service = new Service();
    	if(service.loadService(doc))
    		return service;
    	else
    		return null;
    }
    
    //  Remove a services doc of 'id' from the collection
    public Freedoor.RequestReturnType removeService(String oid) {
    	//Get the services collection
        DBCollection serv_coll = db.getCollection(COLLNAME_SER);
        
        DBObject doc = getServiceObj(oid);
        
        if(doc == null || serv_coll == null)
        	return Freedoor.RequestReturnType.IdNotFound;
        
        WriteResult res = serv_coll.remove(doc);
        
        if(res.getLastError().ok())
        	return Freedoor.RequestReturnType.Success;
        else
        	return Freedoor.RequestReturnType.Fail;
    }
    
    //pageNumber == 0 means no pagination
	public ServiceList getServices(int pageNumber) {
		DBCollection coll = db.getCollection(COLLNAME_SER);
		DBCursor cursor;
		
		if(pageNumber == 0)
			cursor = coll.find();
		else
			cursor = findPage(coll, null, pageNumber);
		
		//Interesting note about the coll.count() here: even with pagination, it still says the same number (collection.find())
		//this must mean that the skip() and limit() methods are altering the cursor, but not the actual collection, which makes sense
		System.out.println("count: " + coll.count());

		ServiceList services = new ServiceList();
		
        try {
            while(cursor.hasNext()) {
            	DBObject obj = cursor.next();
            	Service service = new Service();
            	if(service.loadService(obj))
            		services.add(service);
            }
        } finally {
            cursor.close();
        }
		
		return services;
	}
	
	//pageNumber == 0 means no pagination
		public ServiceList searchServices(String name, String status, int pageNumber) {
			DBCollection coll = db.getCollection(COLLNAME_SER);
			DBCursor cursor;
			
			//	Case 1 : If both name and status specified is null; return all services as per pageNumber
			if ( name.equalsIgnoreCase("") && status.equalsIgnoreCase("") ) {
				return getServices(pageNumber);
			}
			
			if ( status.equalsIgnoreCase("buying") || status.equalsIgnoreCase("buy") ) {
				status = "buying";
			} else if ( status.equalsIgnoreCase("selling") || status.equalsIgnoreCase("sell") ) {
				status = "selling";
			}
					
			//	Do a wild-card search if the key = "name" 
			//	Meaning, filter all services with names even if the 'value' parameter is a sub-string;
			//	Example : if search for name = "mass"; return all services with name = "massage", name = "massive" etc.
			if ( !name.equalsIgnoreCase("") ) {
				//	Create a query to facilitate wild card search.
				//String wildcard_value = value + "*";			// Does not work if we use this.
				Pattern p = Pattern.compile(name, Pattern.CASE_INSENSITIVE);
				BasicDBObject query = new BasicDBObject(KEY_NAME, p);
				
				if ( !status.equalsIgnoreCase("") ) {
					query.put(KEY_STAT, status);
				}
				
				 //	Apply the query and get cursor for result
		        if(pageNumber == 0)
					cursor = coll.find(query);
				else
					cursor = findPage(coll, query, pageNumber);
			} else {
				//	Create the query with the key and value specified.
				BasicDBObject query = new BasicDBObject();
				query.put(KEY_STAT, status);
				
				 //	Apply the query and get cursor for result
		        if(pageNumber == 0)
					cursor = coll.find(query);
				else
					cursor = findPage(coll, query, pageNumber);
			}
	        
			//Interesting note about the coll.count() here: even with pagination, it still says the same number (collection.find())
			//this must mean that the skip() and limit() methods are altering the cursor, but not the actual collection, which makes sense
			System.out.println("count: " + coll.count());

			ServiceList services = new ServiceList();
		        try {
		            while(cursor.hasNext()) {
		              	DBObject obj = cursor.next();
		            	Service service = new Service();
		            	
		            	if(service.loadService(obj)) {
		           			services.add(service);
		            	}
		            }
		        } finally {
		            cursor.close();
		        }

			return services;
		}
	
	private DBObject getServiceObj(String objectId)
    {
		ObjectId idObj;
		try {
			idObj = new ObjectId(objectId);
		} catch(IllegalArgumentException e) {
			return null; //return if invalid format on oid
		}
    	//  Get the services collection
        DBCollection serv_coll = db.getCollection(COLLNAME_SER);
        
        BasicDBObject query = new BasicDBObject();
        //query.put("type", "services");
        query.put("_id", idObj);
        DBObject doc = serv_coll.findOne(query);
        
    	return doc;
    }
	
	/* Offer related methods */
	public OfferList getOffersForService(String oid) {
        DBObject doc = getServiceObj(oid);
        
        Service srv = new Service();
        if(doc == null || !srv.loadService(doc))
        	return null;
        
		return srv.getOffers();
	}
    
    //  Insert offers, return ID of updated service
    public String putOffersForService(String serviceOid, OfferList offers) {
    	if ( offers == null )
    		return "";
    	
    	Service service = getService(serviceOid);
    	if(service.addOffers(offers))
    		return updateService(service);
    	else
    		return "";
    }

    
    
	/*	'Goods' related methods	*/
	
	// Insert a good document under newly generated ObjectID, returns ID
    private String insertGood(Good good) {
    	
    	if ( good == null )
    		return "";
        
        //  Get the goods collection (creates one if one such collection is not present)
        DBCollection serv_coll = db.getCollection(COLLNAME_GDS);
        
        //  Need to parse the 'body' json and get the values.
        BasicDBObject doc = new BasicDBObject();
        
        doc.put("community_url", good.getURL());
        doc.put("id", good.getId());
        doc.put("name", good.getName());
        doc.put("type", good.getType());
        doc.put("description", good.getDescription());
        doc.put("status", good.getStatus());
        doc.put("timestamp", good.getTimeStamp());
        doc.put("offers", good.getOffers().toJsonString(false, false));
        
        serv_coll.insert(doc);
        System.out.println("The inserted goods doc = " + doc.toString());
        
        if(good.getId().isEmpty() && (good.getURL().isEmpty() || good.getURL().equals(Freedoor.SELF_URL))) {
        	doc.put("id", doc.get("_id").toString()); //set to generated ID if local
        	doc.put("community_url", Freedoor.SELF_URL); //set to local URL
        	//good.setId(doc.get("_id").toString()); //set to generated ID if local
        	//good.setURL(Freedoor.SELF_URL); //set to local URL
        	BasicDBObject query = new BasicDBObject();
            query.put("_id", doc.get("_id"));
        	serv_coll.update(query, doc);
        }
        
        return doc.get("_id").toString();
    }
    
	//  Updates a good document if it exists, inserts otherwise
 	// If Local URL and ID matches an existing record, updates record and returns ID of existing record
    // Insert a good document under newly generated ObjectID, returns ID
 	// If error of some kind, returns ""
    public String putGood(Good good) {
    	
    	String updateId = updateGood(good);
        
    	if (updateId.isEmpty())
    		return insertGood(good);
    	else
    		return updateId;
    }
    
    //  Updates a good document if it exists, inserts otherwise
 	// If Local URL and ID matches an existing record, updates record and returns ID of existing record
 	// If error of some kind or no match, returns ""
    public String updateGood(Good good) {
    	if ( good == null )
    		return "";
    	
    	//Try retrieving the good with the good.id
        DBCollection serv_coll = db.getCollection(COLLNAME_GDS);
        
        BasicDBObject query = new BasicDBObject();
        query.put("type", "goods");
        query.put("community_url", good.getURL());
        query.put("id", good.getId());
        DBObject doc = serv_coll.findOne(query);
        
    	if ( doc != null ) {
    		//	Update
            doc.put("community_url", good.getURL());
            doc.put("id", good.getId());
            doc.put("name", good.getName());
            doc.put("type", good.getType());
            doc.put("description", good.getDescription());
            doc.put("status", good.getStatus());
            doc.put("timestamp", good.getTimeStamp());
            doc.put("offers", good.getOffers().toJsonString(false, false));
            
            serv_coll.update(query, doc);
            
            return doc.get("_id").toString();
    	} else
    		return "";
    }
    
    //  Retrieve a goods doc based on 'id'
    public Good getGood(String oid) {
    	DBObject doc = getGoodObj(oid);
    	
    	if(doc == null)
    		return null;
    	
    	Good good = new Good();
    	if(good.loadGood(doc))
    		return good;
    	else
    		return null;
    }
    
    //  Remove a goods doc of 'id' from the collection
    public Freedoor.RequestReturnType removeGood(String oid) {
    	//Get the goods collection
        DBCollection serv_coll = db.getCollection(COLLNAME_GDS);
        
        DBObject doc = getGoodObj(oid);
        
        if(doc == null || serv_coll == null)
        	return Freedoor.RequestReturnType.IdNotFound;
        
        WriteResult res = serv_coll.remove(doc);
        
        if(res.getLastError().ok())
        	return Freedoor.RequestReturnType.Success;
        else
        	return Freedoor.RequestReturnType.Fail;
    }
    
    //pageNumber == 0 means no pagination
	public GoodList getGoods(int pageNumber) {
		DBCollection coll = db.getCollection(COLLNAME_GDS);
		DBCursor cursor;
		
		if(pageNumber == 0)
			cursor = coll.find();
		else
			cursor = findPage(coll, null, pageNumber);
		
		//Interesting note about the coll.count() here: even with pagination, it still says the same number (collection.find())
		//this must mean that the skip() and limit() methods are altering the cursor, but not the actual collection, which makes sense
		System.out.println("count: " + coll.count());

		GoodList goods = new GoodList();
		
        try {
            while(cursor.hasNext()) {
            	DBObject obj = cursor.next();
            	Good good = new Good();
            	if(good.loadGood(obj))
            		goods.add(good);
            }
        } finally {
            cursor.close();
        }
		
		return goods;
	}
	
	//pageNumber == 0 means no pagination
		public GoodList searchGoods(String name, String status, int pageNumber) {
			DBCollection coll = db.getCollection(COLLNAME_GDS);
			DBCursor cursor;
			
			//	Case 1 : If both name and status specified is null; return all goods as per pageNumber
			if ( name.equalsIgnoreCase("") && status.equalsIgnoreCase("") ) {
				return getGoods(pageNumber);
			}
			
			if ( status.equalsIgnoreCase("buying") || status.equalsIgnoreCase("buy") ) {
				status = "buying";
			} else if ( status.equalsIgnoreCase("selling") || status.equalsIgnoreCase("sell") ) {
				status = "selling";
			}
					
			//	Do a wild-card search if the key = "name" 
			//	Meaning, filter all goods with names even if the 'value' parameter is a sub-string;
			//	Example : if search for name = "mass"; return all goods with name = "massage", name = "massive" etc.
			if ( !name.equalsIgnoreCase("") ) {
				//	Create a query to facilitate wild card search.
				//String wildcard_value = value + "*";			// Does not work if we use this.
				Pattern p = Pattern.compile(name, Pattern.CASE_INSENSITIVE);
				BasicDBObject query = new BasicDBObject(KEY_NAME, p);
				
				if ( !status.equalsIgnoreCase("") ) {
					query.put(KEY_STAT, status);
				}
				
				 //	Apply the query and get cursor for result
		        if(pageNumber == 0)
					cursor = coll.find(query);
				else
					cursor = findPage(coll, query, pageNumber);
			} else {
				//	Create the query with the key and value specified.
				BasicDBObject query = new BasicDBObject();
				query.put(KEY_STAT, status);
				
				 //	Apply the query and get cursor for result
		        if(pageNumber == 0)
					cursor = coll.find(query);
				else
					cursor = findPage(coll, query, pageNumber);
			}
	        
			//Interesting note about the coll.count() here: even with pagination, it still says the same number (collection.find())
			//this must mean that the skip() and limit() methods are altering the cursor, but not the actual collection, which makes sense
			System.out.println("count: " + coll.count());

			GoodList goods = new GoodList();
		        try {
		            while(cursor.hasNext()) {
		              	DBObject obj = cursor.next();
		            	Good good = new Good();
		            	
		            	if(good.loadGood(obj)) {
		           			goods.add(good);
		            	}
		            }
		        } finally {
		            cursor.close();
		        }

			return goods;
		}
	
	private DBObject getGoodObj(String objectId)
    {
		ObjectId idObj;
		try {
			idObj = new ObjectId(objectId);
		} catch(IllegalArgumentException e) {
			return null; //return if invalid format on oid
		}
    	//  Get the goods collection
        DBCollection serv_coll = db.getCollection(COLLNAME_GDS);
        
        BasicDBObject query = new BasicDBObject();
        //query.put("type", "goods");
        query.put("_id", idObj);
        DBObject doc = serv_coll.findOne(query);
        
    	return doc;
    }
	
	/* Offer related methods */
	public OfferList getOffersForGood(String oid) {
        DBObject doc = getGoodObj(oid);
        
        Good srv = new Good();
        if(doc == null || !srv.loadGood(doc))
        	return null;
        
		return srv.getOffers();
	}
    
    //  Insert offers, return ID of updated good
    public String putOffersForGood(String goodOid, OfferList offers) {
    	if ( offers == null )
    		return "";
    	
    	Good good = getGood(goodOid);
    	if(good.addOffers(offers))
    		return updateGood(good);
    	else
    		return "";
    }

    
    
    
    /* General purpose methods */
    private static DBCursor findPage(DBCollection collection, BasicDBObject query, int pageNumberToReturn) {
    	if(query == null)
    		return collection.find().skip((pageNumberToReturn - 1) * Freedoor.NUMBER_OF_DOCUMENTS_PER_PAGE).limit(Freedoor.NUMBER_OF_DOCUMENTS_PER_PAGE);
    	else
    		return collection.find(query).skip((pageNumberToReturn - 1) * Freedoor.NUMBER_OF_DOCUMENTS_PER_PAGE).limit(Freedoor.NUMBER_OF_DOCUMENTS_PER_PAGE);
    }
	
	//	Testing all the above methods using a main()
	public static void main(String args[]) {
		MongoHandler db = new MongoHandler();
		db.getCommunities();
		db.getGoods(0);
	}
}
